package adfc2.core;

import adfc2.entities.DroppedItem;
import adfc2.entities.Dwarf;
import adfc2.entities.MasonWorkshop;
import adfc2.entities.Tree;
import adfc2.items.Pickaxe;
import adfc2.items.Stone;

import java.util.*;

public class World {
    public static final int WORLD_WIDTH = 128;
    public static final short MASK_UNDERGROUND = (short) (1 << 15);
    public static final short MASK_FILLED = 1 << 14;
    public static final short MASK_BUILDING = 1 << 13;
    public static final short MASK_IMPASSABLE = 1 << 12;
    public static final short MASK_CONSTRUCTION_WALL = 1 << 11;
    public static final short MASK_CONSTRUCTION_RAMP = 1 << 10;
    public static final short MASK_CONSTRUCTION_LADDER = (1 << 11) | (1 << 10);
    public static final short MASK_CONSTRUCTION_ANY = MASK_CONSTRUCTION_WALL | MASK_CONSTRUCTION_RAMP | MASK_CONSTRUCTION_LADDER;
    public static final short MASK_MATERIAL = (short) ~(MASK_UNDERGROUND | MASK_FILLED | MASK_BUILDING | MASK_IMPASSABLE | MASK_CONSTRUCTION_ANY);
    public static String[] CONSTRUCTION_NAMES = {"", "ramp", "wall", "ladder"};
    public short[] blocks = new short[WORLD_WIDTH * WORLD_WIDTH];
    public static final List<Entity> NO_ENTITIES = Collections.unmodifiableList(new LinkedList<Entity>());
    private LinkedList<Entity> entities = new LinkedList<Entity>();
    private LinkedList<Entity> entities_new = new LinkedList<Entity>();
    private LinkedList<Entity> entities_del = new LinkedList<Entity>();
    private HashMap<Integer, List<Entity>> entities_xy = new HashMap<Integer, List<Entity>>();
    private Random random = new Random();
    private boolean elock = false;
    public int time = 0;
    public final ArrayList<Object> global_vars = new ArrayList<Object>();
    private final Map<String, Integer> global_var_map = new HashMap<String, Integer>();
    private int global_var_index = 0;

    public static String constructionName(short construction) {
        return CONSTRUCTION_NAMES[construction >> 10];
    }

    public List<Entity> entities() {
        return Collections.unmodifiableList(entities);
    }

    public World() {
        world = this;
    }

    public void remBlock(int tgtxy1) {
        blocks[tgtxy1] &= ~(MASK_FILLED | MASK_IMPASSABLE);
    }

    public List<Entity> entitiesAt(int xy) {
        List<Entity> list = entities_xy.get(xy);
        if (list == null) return NO_ENTITIES;
        return list;
    }

    public void putEntityTo(Entity entity, int xy) {
        unmapEntity(entity);
        List<Entity> list = entities_xy.get(xy);
        if (list == null) {
            list = new LinkedList<Entity>();
            entities_xy.put(xy, list);
        }
        list.add(entity);
        entity.setXY(xy);
    }

    protected void unmapEntity(Entity entity) {
        if (entity.getXY() >= 0) entities_xy.get(entity.getXY()).remove(entity);
    }

    public void registerEntity(Entity entity, int xy) {
        if (elock) entities_new.add(entity);
        else entities.add(entity);
        putEntityTo(entity, xy);
    }

    public void unregisterEntity(Entity entity) {
        unmapEntity(entity);
        if (elock) entities_del.add(entity);
        else entities.remove(entity);
    }

    public static int mkxy(int x, int y) {
        return x + y * WORLD_WIDTH;
    }

    public static int xy2x(int xy) {
        return xy % WORLD_WIDTH;
    }

    public static int xy2y(int xy) {
        return xy / WORLD_WIDTH;
    }

    public static int dist2(int xy1, int xy2) {
        int dx = xy2x(xy1) - xy2x(xy2);
        int dy = xy2y(xy1) - xy2y(xy2);
        return dx * dx + dy * dy;
    }

    public static boolean near(int xy1, int xy2) {
        int dx = xy2x(xy1) - xy2x(xy2);
        int dy = xy2y(xy1) - xy2y(xy2);
        return (dx >= -1) && (dx <= 1) && (dy >= -1) && (dy <= 1);
    }

    public int rnd() {
        return random.nextInt();
    }

    public int rnd(int n) {
        return random.nextInt(n);
    }

    public boolean chance(int n, int of) {
        return rnd(of) < n;
    }

    public void mkblocks() {
        int ww2 = WORLD_WIDTH / 2;
        for (int x = 0; x < WORLD_WIDTH; x++) {
            for (int y = 0; y < ww2; y++) {
                blocks[mkxy(x, y)] = Material.GENERIC_SOIL.ord();
                if (chance(1, 20) && (x < ww2 - 5 || x > ww2 + 5 || y < ww2 - 5))
                    registerEntity(new Tree(), mkxy(x, y));
            }
            for (int y = ww2; y < WORLD_WIDTH; y++) {
                blocks[mkxy(x, y)] = (short) (MASK_UNDERGROUND | MASK_FILLED | MASK_IMPASSABLE | (chance(1, 10) ? Material.HEMATITE.ord() : Material.GRANITE.ord()));
            }
        }
        /*for (Entity entity : entitiesAt(mkxy(ww2 - 1, ww2 - 1))) entity.remove();
        for (Entity entity : entitiesAt(mkxy(ww2 - 2, ww2 - 1))) entity.remove();
        for (Entity entity : entitiesAt(mkxy(ww2 + 1, ww2 - 2))) entity.remove();*/

        new Dwarf().register(mkxy(ww2 - 1, ww2 - 1));
        new DroppedItem(new Pickaxe(Material.IRON)).register(mkxy(ww2 - 2, ww2 - 1));
        new MasonWorkshop(new Stone(Material.GRANITE)).register(mkxy(ww2 + 1, ww2 - 4));
        placeWall(mkxy(ww2 + 1, ww2 - 1), Material.GRANITE.ord());
    }

    public void tick(long dt) {
        elock();
        for (Entity entity : entities) {
            entity.tick(dt);
        }
        esync();
        time += dt;
    }

    private void esync() {
        elock = false;
        for (Entity entity : entities_del) {
            entities.remove(entity);
        }
        for (Entity entity : entities_new) {
            entities.add(entity);
        }
        entities_new.clear();
        entities_del.clear();
    }

    private void elock() {
        elock = true;
    }

    private static World world;

    public static World world() {
        return world;
    }

    public int registerGlobalVar(String name, Object defval) {
        Integer index = global_var_map.get(name);
        if (index == null) {
            index = global_var_index++;
            global_var_map.put(name, index);
            global_vars.add(defval);
        }
        return index;
    }

    public static boolean passable(short block) {
        return (block & MASK_IMPASSABLE) == 0;
    }

    public void placeWall(int txy, short material) {
        blocks[txy] = (short) (MASK_CONSTRUCTION_WALL | MASK_IMPASSABLE | material | (blocks[txy] & MASK_UNDERGROUND));
    }

    public void placeLadder(int txy, short material) {
        blocks[txy] = (short) (MASK_CONSTRUCTION_LADDER | material | (blocks[txy] & MASK_UNDERGROUND));
    }

    public void placeRamp(int txy, short material) {
        blocks[txy] = (short) (MASK_CONSTRUCTION_RAMP | material | (blocks[txy] & MASK_UNDERGROUND));
    }
}